#pragma once

#include "GameMap.h"
#include "BeanieCore.h"
#include "FileParse.h"
#include "FolderParse.h"
#include "Graph.h"
#include "astar.h"

#include "GameHeader.h"

#define SPRITE_SQUARE	64
struct GraphTest
{
	TemplateVector<V2DF> start;
	TemplateVector<V2DF> end;
	void release()
	{
		start.release();
		end.release();
	}
	void clear()
	{
		start.clear();
		end.clear();
	}
};

class MapManager
{
private:
	TemplateVector<map*> m_maps;
	BeanieCore* m_core;
	FileGetter m_folder;
	FileParser m_parser;
	TemplateVector<GameTexture> textures;
	GraphTest floorGraphTest;
	Graph<mapNode> graph;
	Graph<mapNode> m_walls;
	GameObject Alert1;
	GameObject Alert2;
	GameObject Alert3;
	GameObject Alert4;
	int currentMap;
	BeanieDebug debug;
	clkWorld* m_world;
	Astar m_star;
	bool isAlerted;

	//a_tiles: Which set of tiles to reference
	//a_nodes: what nodes to fill
	//tile: Which char(s) should be connected
	void fillGraph(TemplateVector<char*> & a_tiles, TemplateVector<GraphNode<mapNode>*> & a_nodes, char tile)
	{
		if(a_nodes.size())
			return;
		int length = a_tiles.size();
		for (int i = 0; i < length; i++)
		{
			int width = strlen(a_tiles[i]);
			for (int j = 0; j < width; j++)
			{
				char c = a_tiles[i][j];
				if(c == tile)
				{
					GraphNode<mapNode>* a_node;
					a_node = new GraphNode<mapNode>;
					mapNode newNode;
					newNode.m_id = i+j;
					newNode.pos = V2DI(j,i);
					a_node->value = newNode;
					a_nodes.add(a_node);
				}
			}
		}
	}

	void fillConnections(TemplateVector<GraphNode<mapNode>*> & a_nodes){
		int length = a_nodes.size();
		for (int i = 0; i < length; i++)
		{
			GraphNode<mapNode>* iNode = a_nodes[i];
			for (int j = 0; j < length; j++)
			{
				GraphNode<mapNode>* jNode = a_nodes[j];
				if(((iNode->value.pos.y == (jNode->value.pos.y-1))
					&&(iNode->value.pos.x == jNode->value.pos.x))||
					((iNode->value.pos.y == (jNode->value.pos.y+1))
					&&(iNode->value.pos.x == jNode->value.pos.x)))
				{
					graph.connectTwoWay(iNode,jNode);
				}
				else if(((iNode->value.pos.x == (jNode->value.pos.x-1))
					&&(iNode->value.pos.y == jNode->value.pos.y))||
					((iNode->value.pos.x == (jNode->value.pos.x+1))
					&&(iNode->value.pos.y == jNode->value.pos.y)))
				{
					graph.connectTwoWay(iNode,jNode);
				}
			}
		}
	}

	void findEntities(int map)
	{
		int mapy = m_maps[map]->obj.size();
		char tile;
		for (int i = 0; i < mapy; i++)
		{
			int mapx = strlen(m_maps[map]->obj[i]);
			for (int j = 0; j < mapx; j++)
			{
				tile = m_maps[map]->obj[i][j];
				if(tile == '&')
					m_maps[map]->ent_player = V2DF((float)j*SPRITE_SQUARE,(float)i*SPRITE_SQUARE);
				else if (tile == '!')
					m_maps[map]->ent_enemies.insertBack(V2DF((float)j*SPRITE_SQUARE,(float)i*SPRITE_SQUARE));
				else if (tile == '^')
					createObject(V2DF((float)j*SPRITE_SQUARE,(float)i*SPRITE_SQUARE),m_maps[map]->ent_objective);
				else if (tile == '$')
					createObject(V2DF((float)j*SPRITE_SQUARE,(float)i*SPRITE_SQUARE),m_maps[map]->ent_exit);
			}
		}
	}

	void createObject(V2DF a_pos, GameObject*& a_object)
	{
		clkObjectDef tDef;
		if(!a_object)
			a_object = new GameObject;
		{
			tDef.m_type = SHAPE_RECT;
			tDef.m_width = 64.0f;
			tDef.m_height =64.0f;


			tDef.m_mass = 1.0f;
			tDef.m_iner = 1.0f;

			tDef.m_lnVel.set(V2D_ZERO);
			tDef.m_lnDamp = 5.0f;
			tDef.m_mxLnFrc = 512.0 * 3.0f;

			tDef.m_agVel = 0.0f;
			tDef.m_agDamp = 10.0f;
			tDef.m_mxAgFrc = 512.0f * 2.0f;

			tDef.m_isAsleep = false;
			tDef.m_isCollidable = false;
			tDef.m_isStationary = true;
		}

		//create enemy
		tDef.m_pos.set(a_pos);

		a_object->initBody(m_world->createObject(tDef));
		a_object->initTextureX(m_core,SPR_DRONE,V2D_ONES,0.0f);

	}

	void findPaths(char* file, List<GameEnemy*>& a_enemies, int map)
	{
		TemplateVector<char*> paths;
		m_parser.printFileToVector(paths, '%', file);
		V2DF to = V2D_ZERO;
		V2DF from = V2D_ZERO;
		int length = paths.size();
		bool isFrom = true;
		for (int i = 0; i < length; i++)
		{
			isFrom = true;
			int length2 = strlen(paths[i]);
			int count = 0;
			char temp[5];
			ZeroMemory(temp,5);
			for (int j = 0; j < length2; j++)
			{
				if(paths[i][j] >= '0'&& paths[i][j] <= '9')
				{
					temp[count] = paths[i][j];
					count++;
				}
				if(paths[i][j] == ',')
				{
					if(isFrom)
					{
						from.x = atoi(temp)*64.0f;
						ZeroMemory(temp,5);
						count = 0;
					}
					else
					{
						to.x = atoi(temp)*64.0f;
						ZeroMemory(temp,5);
						count = 0;
					}
				}
				else if(paths[i][j] == '-')
				{
					if(isFrom)
					{
						from.y = atoi(temp)*64.0f;
						ZeroMemory(temp,5);
						isFrom = false;
						count = 0;
					}
				}
				else if(paths[i][j] == ' ' || paths[i][j] == '\0' ||
					paths[i][j] == '\n' || j == length2-1)
				{
					if(!isFrom)
					{
						to.y = atoi(temp)*64.0f;
						ZeroMemory(temp,5);
						count = 0;
					}
				}
					
			}
			if(a_enemies.size() > i)
				m_star.findPath(closestNode(to, map),closestNode(from, map),a_enemies[i]->getPath());
		}
	}

	void allocateEnemies(List<GameEnemy*>& a_enemies,List<V2DF>& ent_enemies, GamePlayer* a_player)
	{
		int length = a_enemies.size();

		clkObjectDef tDef;

		{
			tDef.m_type = SHAPE_RECT;
			tDef.m_width = 24.0f;
			tDef.m_height = 24.0f;

			tDef.m_mass = 1.0f;
			tDef.m_iner = 1.0f;

			tDef.m_lnVel.set(V2D_ZERO);
			tDef.m_lnDamp = 10.0f;
			tDef.m_mxLnFrc = 768.0f;

			tDef.m_agVel = 0.0f;
			tDef.m_agDamp = 10.0f;
			tDef.m_mxAgFrc = 512.0f;

			tDef.m_isAsleep = false;
			tDef.m_isCollidable = true;
			tDef.m_isStationary = false;
		}

		//create enemy
		GameEnemy* tObj = 0;
		for(int i = 0; i < ent_enemies.size(); i++)
		{
			tDef.m_pos.set(ent_enemies[i]);
			tDef.m_rot = randomFloat(0.0f,360.0f);

			tObj = new GameEnemy();
			tObj->initBody(m_world->createObject(tDef));
			tObj->initTexture(m_core,SPR_PLAYER,TEX_CENTER,V2D_ZERO,V2D_ONES,0.0f,false,4,4);
			tObj->initEnemy(100.0f,96.0f,192.0f);
			tObj->initTarget(a_player);
			tObj->initLine(m_world->getList());

			a_enemies.insertBack(tObj);
		}
		waitEnemies(a_enemies);
	}

	void waitEnemies(List<GameEnemy*>& a_enemies)
	{
		int length = a_enemies.size();
		for (int i = 0; i < length; i++)
		{
			m_world->waitObject(a_enemies[i]->m_body);
		}
	}

	void wakeEnemies(List<GameEnemy*>& a_enemies)
	{
		int length = a_enemies.size();
		for (int i = 0; i < length; i++)
		{
			m_world->pullObject(a_enemies[i]->m_body);
		}
	}

	void initPlayer(GamePlayer*& a_player, V2DF playerPos)
	{
		clkObjectDef tDef;
		isAlerted = false;
		if(a_player == 0)
		{
			a_player = new GamePlayer;
			tDef.m_type = SHAPE_RECT;
			tDef.m_width = 24.0f;
			tDef.m_height = 24.0f;

			tDef.m_pos.set(playerPos);
			tDef.m_rot = 0.0f;

			tDef.m_mass = 1.0f;
			tDef.m_iner = 1.0f;

			tDef.m_lnVel.set(V2D_ZERO);
			tDef.m_lnDamp = 10.0f;
			tDef.m_mxLnFrc = 1024.0f * 3.0f;

			tDef.m_agVel = 0.0f;
			tDef.m_agDamp = 10.0f;
			tDef.m_mxAgFrc = 512.0f;

			tDef.m_isAsleep = false;
			tDef.m_isCollidable = true;
			tDef.m_isStationary = false;

			a_player->initBody(m_world->createObject(tDef));
			a_player->initTexture(m_core,SPR_PLAYER,TEX_CENTER,V2D_ZERO,V2D_ONES,0.0f,false,4,4);
			a_player->initPlayer(100.0f,100.0f);
			m_world->waitObject(a_player->m_body);
		}
		else
		{
			a_player->m_body->m_pos.set(playerPos);
			m_world->pullObject(a_player->m_body);
		}
	}
	
	void physTest()
	{
		int length = m_maps[currentMap]->tileId.size();
		for (int i = 0; i < length; i++)
		{
			int length2 = m_maps[currentMap]->p_walls.size();
			for (int j = 0; j < length2; j++)
			{
				drawRect(m_core,m_maps[currentMap]->p_walls[j]->m_pos.x,
					V2DF(),m_maps[currentMap]->p_walls[j]->m_vertR,CL_GREEN);

			}
		}
	}

	void mapDebug()
	{
		debug.clear();
		int length = m_maps[currentMap]->tileset.size();
		for (int i = 0; i < length; i++)
		{
			debug.watch(m_maps[currentMap]->tileset[i]);
		}
	}

	void convertMap(TemplateVector<char*>& tiles, TemplateVector<TextureMap*>& tileId)
	{
		int length = tiles.size();
		char* newTiles;
		for (int i = 0; i < length; i++)
		{
			newTiles = new char[512];
			ZeroMemory(newTiles,512);
			int length2 = strlen(tiles[i]);
			TextureMap* newMap = new TextureMap;
			int pos = 0;
			for (int j = 0; j < length2; j++)
			{
				if (tiles[i][j] >= '0' && tiles[i][j] <= '9' || tiles[i][j] == ' ')
				{
					int value = tiles[i][j]-'0';
					newMap->textId.add(value);
					if(value)
						value = -1;
				}
			}
			if(newMap->textId.size())
			{
				tileId.add(newMap);
			}
			else
				delete newMap;
			delete [] tiles[i];
			tiles[i] = newTiles;
		}
	}

	void addPhysObj(TemplateVector<clkObject*>& objs)
	{
		int length = objs.size();
		for (int i = 0; i < length; i++)
		{
			m_world->pullObject(objs[i]);
		}
	}

	void removePhysObj(TemplateVector<clkObject*>& objs)
	{
		int length = objs.size();
		for (int i = 0; i < length; i++)
		{
			m_world->waitObject(objs[i]);
		}
	}

	void findTextures(char* folder, TemplateVector<GameTexture*>& a_texture)
	{
		TemplateVector<char*> textures;
		TemplateVector<char*> sorted = m_folder.sortFolder(textures,folder,".png",true);
		int length = sorted.size();
		for (int i = 0; i < length; i++)
		{
			GameTexture* texture = new GameTexture;
			texture->initTexture(m_core,sorted[i],TEX_CENTER,V2D_ZERO,V2D_ONES,0,false);
			a_texture.add(texture);
		}
		SAFE_RELEASE_LIST(textures);
		sorted.release();
		//SAFE_RELEASE_LIST(textures);
	}

	void createWallObj(TemplateVector<char*>& a_tiles, TemplateVector<clkObject*>& a_walls)
	{
		clkObjectDef t_wall;
		t_wall.m_type = SHAPE_RECT;
		t_wall.m_height = SPRITE_SQUARE;
		t_wall.m_width = SPRITE_SQUARE;
		t_wall.m_isCollidable = true;
		t_wall.m_isStationary = true;
		t_wall.m_rot = 0;

		int length = a_tiles.size();
		for (int i = 0; i < length; i++)
		{
			int size = strlen(a_tiles[i]);
			for (int j= 0; j < size; j++)
			{
				if(a_tiles[i][j] == '#')
				{
					t_wall.m_pos = V2DF((float)j*SPRITE_SQUARE, (float)i*SPRITE_SQUARE);
					a_walls.add(m_world->createObject(t_wall));
					m_world->waitObject(a_walls.getLast());
				}
			}
		}
	}

	void graphTest(int map,GraphTest& a_graph)
	{
		TemplateVector<char*> maps;
		int length3 = m_maps[map]->tileset.size();
		debug.clear();
		debug.watch(" ");
		debug.watch(" ");
		debug.watch(" ");
		debug.watch(" ");
		for (int i = 0; i < length3; i++)
		{
			char* v = new char[strlen(m_maps[map]->tileset[i])];
			fillString(v,' ');
			maps.add(v);
		}
		int length4 = maps.size();
		for (int i = 0; i < length4; i++)
		{
			debug.watch(maps[i]);
		}
		int length = m_maps[map]->m_nodes.size();
		for (int i = 0; i < length; i++)
		{
			GraphNode<mapNode>* currentNode = m_maps[map]->m_nodes[i];
			int length2 = currentNode->connections.size();
			for (int j = 0; j < length2; j++)
			{
				maps[currentNode->value.pos.y][currentNode->value.pos.x] = '#';
				float x = currentNode->value.pos.x*64.0f;
				float y = currentNode->value.pos.y*64.0f;
				a_graph.start.add(V2DF(x,y));
				x = currentNode->connections[j].to->value.pos.x*64.0f;
				y = currentNode->connections[j].to->value.pos.y*64.0f;
				a_graph.end.add(V2DF(x,y));
			}
		}
	}
public:
	MapManager(){}
	~MapManager(){release();}

	void release()
	{
		if (m_maps.size() != 0)
		{
			for (int i = 0; i < m_maps.size(); i++)
			{
				m_maps[i]->release();
			}
		}
		SAFE_RELEASE_LIST(m_maps);
		textures.release();
		floorGraphTest.release();
		debug.release();
		m_star.release();
	}

	int init(BeanieCore* a_core, clkWorld* a_world)
	{
		m_core = a_core;
		m_world = a_world;
		currentMap = 0;
		TemplateVector<char*> files;
		TemplateVector<char*> sorted = m_folder.sortFolder(files,"maps/",".map",true);
		for (int i = 0; i < sorted.size(); i++)
		{
			TemplateVector<char*> tiles;
			char* file = new char[strlen(sorted[i])-4+2];
			char* fileW = new char[256];
			char* fileF = new char[256];
			m_maps.add(new map());
			copyStringSegment(0,strlen(sorted[i])-4,file,sorted[i]);
			sprintf_s(fileW,256,"%s/wall/",file);
			sprintf_s(fileF,256,"%s/floor/",file);
			m_maps[i]->m_file = file;
			findTextures(fileF, m_maps[i]->t_floors);
			findTextures(fileW, m_maps[i]->t_walls);

			m_parser.printFileToVector(m_maps[i]->tileset,'/',sorted[i]);
			m_parser.printFileToVector(tiles,':',sorted[i]);
			convertMap(tiles,m_maps[i]->tileId);

			createWallObj(m_maps[i]->tileset,m_maps[i]->p_walls);
			fillGraph(m_maps[i]->tileset,m_maps[i]->m_nodes,'.');
			fillGraph(m_maps[i]->tileset,m_maps[i]->m_walls,'#');
			fillConnections(m_maps[i]->m_nodes);
			fillConnections(m_maps[i]->m_walls);

			m_parser.printFileToVector(m_maps[i]->obj,'-',sorted[i]);

			findEntities(i);

			initPlayer(m_maps[i]->m_player, m_maps[i]->ent_player);
			allocateEnemies(m_maps[i]->m_enemies,m_maps[i]->ent_enemies,m_maps[i]->m_player);
			findPaths(sorted[i],m_maps[i]->m_enemies, i);
			SAFE_RELEASE_TVECTOR_POINTER(tiles);
			delete [] fileW;
			delete [] fileF;
		}
		SAFE_RELEASE_TVECTOR_POINTER(files);
		sorted.release();

		addPhysObj(m_maps[currentMap]->p_walls);
		wakeEnemies(m_maps[currentMap]->m_enemies);
		initPlayer(m_maps[currentMap]->m_player,m_maps[currentMap]->ent_player);

		debug.init(m_core,CL_RED);
		//mapDebug();
		return 0;
	}

	void render(){
		
		m_maps[currentMap]->draw();
		int length = m_maps[currentMap]->m_enemies.size();
		for (int i = 0; i < length; i++)
		{
			m_maps[currentMap]->m_enemies[i]->draw();
		}
		//physTest()
		debug.drawTLD();
		for(int i = 0; i < floorGraphTest.start.size(); i++)
		{
			m_core->drawLine(floorGraphTest.start[i],floorGraphTest.end[i],CL_BLUE);
		}
	}
	
	void update(){

		int length = m_maps[currentMap]->m_enemies.size();
		for (int i = 0; i < length; i++)
		{
			if(m_maps[currentMap]->m_enemies[i]->enemyUpdate())
			{
				isAlerted = true;
			}
		}
	}

	GraphNode<mapNode>* closestNode(V2DF a_pos, int map)
	{
		V2DF m_pos = V2DF(a_pos.x/64.0f,a_pos.y/64.0f);
		GraphNode<mapNode>* returnNode = 0;
		float distance = .7f;
		while(!returnNode)
		{
			int length = m_maps[map]->m_nodes.size();
			for (int i = 0; i < length; i++)
			{
				returnNode = m_maps[map]->m_nodes[i];
				if(returnNode->value.pos == V2DI(16,2))
					int b = 3;
				if((m_pos.x > returnNode->value.pos.x-distance && 
					m_pos.x < returnNode->value.pos.x+distance) &&
					(m_pos.y > returnNode->value.pos.y-distance && 
					m_pos.y < returnNode->value.pos.y+distance))
				{
					V2DI xy = returnNode->value.pos;
					break;
				}
				else
					returnNode = 0;

			}
			if(!returnNode)
				distance += .3f;
		}
		return returnNode;
	}

	GamePlayer* currentPlayer(){return m_maps[currentMap]->m_player;}
	GameObject* objectiveLoc(){return m_maps[currentMap]->ent_objective;}
	GameObject* escapeLoc(){return m_maps[currentMap]->ent_exit;}

	void changeLevel(int a_level)
	{
		m_world->waitObject(m_maps[currentMap]->m_player->m_body);
		waitEnemies(m_maps[currentMap]->m_enemies);

		removePhysObj(m_maps[currentMap]->p_walls);
		currentMap = a_level;
		if(currentMap >= m_maps.size())
			currentMap = m_maps.size()-1;
		else if(currentMap <= 0)
			currentMap = 0;

		addPhysObj(m_maps[currentMap]->p_walls);
		initPlayer(m_maps[currentMap]->m_player,m_maps[currentMap]->ent_player);
		m_maps[currentMap]->posReset();
		wakeEnemies(m_maps[currentMap]->m_enemies);
		//mapDebug();
		//graphTest(currentMap,floorGraphTest);
	}

	int nextLevel()
	{
		m_world->waitObject(m_maps[currentMap]->m_player->m_body);
		waitEnemies(m_maps[currentMap]->m_enemies);
		removePhysObj(m_maps[currentMap]->p_walls);
		currentMap++;
		if(currentMap >= m_maps.size())
			currentMap = 0;

		addPhysObj(m_maps[currentMap]->p_walls);
		wakeEnemies(m_maps[currentMap]->m_enemies);
		initPlayer(m_maps[currentMap]->m_player,m_maps[currentMap]->ent_player);
		m_maps[currentMap]->posReset();
		//mapDebug();
		//graphTest(currentMap,floorGraphTest);
		return currentMap;
	}

	int getLevel(){return currentMap;}
	bool alerted(){return isAlerted;}
	void setAlerted(bool tf){isAlerted=tf;}
};